Skip to content

feat: un-redacted physical-device logs — wire IOSDeviceConsoleLogSource#15

Draft
DevSrSouza wants to merge 1 commit into
mainfrom
feat/ios-device-unredacted-logs
Draft

feat: un-redacted physical-device logs — wire IOSDeviceConsoleLogSource#15
DevSrSouza wants to merge 1 commit into
mainfrom
feat/ios-device-unredacted-logs

Conversation

@DevSrSouza

Copy link
Copy Markdown
Contributor

Problem

On a physical iPhone, every os_log with a private argument showed as <private>. The only source wired for physical devices is the passive LoggingSupport relay (IOSDeviceOSLogSource), and the device's logd redacts private data before it ever reaches the relay — same reason Console.app shows <private> for device logs.

The un-redacting source already existed — IOSDeviceConsoleLogSource — but commit #11 deliberately left it "retained but unwired … for a future opt-in". Git confirms it was never instantiated by any caller on any branch. So this isn't a regression; the opt-in was simply never built.

Change

Swap the physical-device primary source based on the target:

Target Source Result
none (whole device) IOSDeviceOSLogSource (passive) real levels, no launch, but <private>
an app IOSDeviceConsoleLogSource launches it under devicectl --console with OS_ACTIVITY_DT_MODE=enableun-redacted os_log + print()

One source per mode → no duplicate lines (unlike an additive design, which would show each app line twice: redacted via passive + un-redacted via console). The app picker now passes the bundle id (the console launches by bundle id) instead of the display name.

Tradeoff: selecting an app (re)launches it and waits for the device to be unlocked — that's intrinsic; it's the only way to tap an app's un-redacted output without a debugger or a logging profile.

Validation

This console path had never actually run (dead code since #11), so I validated the exact devicectl command on a connected iPhone 12 (iOS 26.5):

  • Passive stream: every app line <private>.
  • Console launch: 845 lines, 0 <private> — real content (company IDs, paths) and the app's own 🟢 (Category) Logger format that IOSDeviceConsoleParser already decodes (glyph → level, (Category) → tag).

🤖 Generated with Claude Code

Physical-device os_log showed every private argument as <private>: the only wired
source was the passive LoggingSupport relay, and logd redacts before it sends. The
un-redacting source -- IOSDeviceConsoleLogSource -- has existed since #11 but was
"retained but unwired" for a future opt-in and was never connected to a session.

Wire it by swapping the physical-device primary per target:
- empty target  -> passive whole-device LoggingSupport stream (real levels, no launch)
- a bundle id   -> launch the app under `devicectl --console` with
                   OS_ACTIVITY_DT_MODE=enable, mirroring that app's os_log un-redacted
                   plus print()/stdout (the Xcode-console experience).

One source per mode, so no duplicate lines. The app picker now passes the bundle id
(the console launches by bundle id) rather than the display name.

Tradeoff: selecting an app (re)launches it and waits for the device to be unlocked --
by design, the only way to tap un-redacted app output without a debugger or a logging
profile.

Validated on a connected iPhone 12 (iOS 26.5): the devicectl console launch yields
0 <private> lines (vs. all-private passive), real content + the app's glyph/category
Logger format the parser already handles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@DevSrSouza DevSrSouza marked this pull request as draft June 17, 2026 02:41
@DevSrSouza

Copy link
Copy Markdown
Contributor Author

Drafting for now. Investigation found this console-launch path has two inherent limits vs. the passive LoggingSupport stream:

  • DEBUG/VERBOSE are droppedOS_ACTIVITY_DT_MODE mirrors INFO+ but gates OS_LOG_TYPE_DEBUG (verified: 0 debug glyphs across ~700 captured mirror lines, both enable and YES).
  • No real levels — the stderr mirror carries no level token, so levels are approximated from the app's 🟢/🟡/🔴 glyphs.

The cleaner fix is app-side: enable Kermit's publicLogging=true (%{public}s, touchlab/Kermit#408), gated to debug builds. That un-redacts at the source, so the existing passive structured stream (already on main) shows un-redacted content with real levels and DEBUG included, no app relaunch.

If revisited, repurpose this PR: keep the passive stream as the default for a targeted app, and put console-launch behind an explicit opt-in toggle — still useful for print()/stdout (the os_log stream can't see it) and for third-party apps that can't be made public.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant